#!python
# -*- coding:utf-8 -*-
u'''
公用函数(字符串处理)
Created on 2014/7/16
@author: Holer
'''
import re
import sys
import json
import logging # 由于 xlog 引用本文件,故不能用 xlog
import time, datetime
import gzip, StringIO, zlib

__all__=('to_unicode', 'to_str', 'to_human', 'to_json', 'json2str', 'mod',
         'gzip_encode', 'gzip_decode', 'zlib_encode', 'zlib_decode')

def to_unicode(value, all2str=False, **kwargs):
    u"""
    @summary: 将字符转为 unicode 编码
    @param {任意} value 将要被转码的值,类型可以是:str,unicode,int,long,float,double,dict,list,tuple,set,其它类型
    @param {boolean} all2str: 是否要将数值、日期、布尔等类型也转成字符串,(list,tuple,set,dict等类型会保持不变,只转换里面的值), 默认不转换
                                    值为 True 时,使用 unicode(value) 来转,值为 "time" 时会附加转换时间类型
    @param {boolean} to_human: 是否要将字符串转码成便于人阅读的编码(将 “\u65f6”,“\xE5\x8C\x85”等字符转为人可以阅读的文字), 默认不转换
    @param {string} from_code: 传入字符串的可能编码类型,如果有则优先按它解码
    @return {unicode|type(value)}: 返回转成 unicode 的字符串,或者原本的参数类型(list,tuple,set,dict等类型会保持不变)
    """
    if value == None:
        return u'' if all2str == True else value
    # str/unicode 类型的
    elif isinstance(value, basestring):
        # str类型,需要按它原本的编码来解码出 unicode,编码不对会报异常
        if isinstance(value, str):
            from_code = kwargs.get('from_code')
            for encoding in (from_code, "utf-8", "gbk", "big5", sys.getdefaultencoding(), "cp936", "latin-1", "ascii"):
                if not encoding or not isinstance(encoding, basestring): continue
                try:
                    value = value.decode(encoding)
                    break # 如果上面这句执行没报异常，说明是这种编码
                except:
                    pass
        # 上面已经转码成 Unicode 的
        str_to_human = kwargs.get('to_human', False) # 避免与 to_human 函数的名称冲突
        if str_to_human and value:
            try:
                # eval 处理是为了让“\u65f6”,“\xE5\x8C\x85”等字符转为人可以阅读的文字
                if "'''" not in value:
                    value = eval(u"u'''%s'''" % value)
                elif '"""' not in value:
                    value = eval(u'u"""%s"""' % value)
                else:
                    value = json.dumps(value, ensure_ascii=False)
                    value = value.replace(r"\\u", r"\u") # json.dumps 会转换“\”,使得“\u65f6”变成“\\u65f6”
                    value = eval(u'u%s' % value)
            except:
                logging.error(u'将字符串转成可阅读编码出错:%s' % value)
        return value
    # 考虑是否需要转成字符串的类型
    elif isinstance(value, (bool,int,long,float,complex)):
        return unicode(value) if all2str == True else value
    # time, datetime 类型转成字符串,需要写格式(不能使用 json.dumps,会报错)
    elif isinstance(value, time.struct_time):
        return time.strftime('%Y-%m-%d %H:%M:%S', value) if all2str in (True, 'time') else value
    elif isinstance(value, datetime.datetime):
        return value.strftime('%Y-%m-%d %H:%M:%S') if all2str in (True, 'time') else value
    elif isinstance(value, datetime.date):
        return value.strftime('%Y-%m-%d') if all2str in (True, 'time') else value
    # list,tuple,set 类型,递归转换
    elif isinstance(value, (list,tuple,set)):
        arr = [to_unicode(item, all2str=all2str, **kwargs) for item in value]
        # 尽量不改变原类型
        if isinstance(value, list):  return arr
        if isinstance(value, tuple): return tuple(arr)
        if isinstance(value, set):   return set(arr)
    # dict 类型,递归转换(字典里面的 key 也会转成 unicode 编码)
    elif isinstance(value, dict):
        this_value = {} # 不能改变原参数
        for key1,value1 in value.items():
            # 字典里面的 key 也转成 utf8 编码
            if isinstance(key1, str):
                key1 = to_unicode(key1, all2str=all2str, **kwargs)
            this_value[key1] = to_unicode(value1, all2str=all2str, **kwargs)
        return this_value
    # 其它类型
    else:
        return unicode(value) if all2str == True else value

def to_str(value, encode="utf-8", **kwargs):
    u"""
    @summary: 将字符转为utf8编码
    @param {任意} value: 将要被转码的值,类型可以是:str,unicode,int,long,float,double,dict,list,tuple,set,其它类型
    @param {string} encode: 编码类型,默认是 utf-8 编码
    @param {boolean} all2str: 是否要将数值类型、日期类型、布尔类型等类型也转成字符串,(list,tuple,set,dict等类型会保持不变,只转换里面的值), 默认不转换
    @param {boolean} to_human: 是否要将字符串转码成便于人阅读的编码(将 “\u65f6”,“\xE5\x8C\x85”等字符转为人可以阅读的文字), 默认不转换
    @param {string} from_code: 传入字符串的可能编码类型,如果有则优先按它解码
    @return {str|type(value)}: 返回转成 str 的字符串,或者原本的参数类型(list,tuple,set,dict等类型会保持不变)
    """
    # 字符串类型的,先转成 unicode,再转成 utf8 编码的 str,这样就可以避免编码错误了
    if isinstance(value, basestring):
        return to_unicode(value, **kwargs).encode(encode)
    # list,tuple,set 类型,递归转换
    elif isinstance(value, (list,tuple,set)):
        arr = [to_str(item, encode=encode, **kwargs) for item in value]
        # 尽量不改变原类型
        if isinstance(value, list):  return arr
        if isinstance(value, tuple): return tuple(arr)
        if isinstance(value, set):   return set(arr)
    # dict 类型,递归转换(字典里面的 key 也会转成 utf8 编码)
    elif isinstance(value, dict):
        this_value = {} # 不能改变原参数
        for key1,value1 in value.items():
            # 字典里面的 key 也转成 utf8 编码
            if isinstance(key1, unicode):
                key1 = to_str(key1, encode=encode, **kwargs)
            this_value[key1] = to_str(value1, encode=encode, **kwargs)
        return this_value
    # 其它类型,可以部分地交给 to_unicode 处理
    return to_unicode(value, **kwargs).encode(encode) if kwargs.get('all2str') else value

def to_human(value, isJson = False, **kwargs):
    u'''
    @summary: 将 字符串/其他值 按便于人阅读的形式展示
        类似于 repr 函数,但同时会将 “\u65f6”,“\xE5\x8C\x85”等字符转为人可以阅读的文字
    @param {任意} value: 将要被转码的值,类型可以是:str,unicode,int,long,float,double,dict,list,tuple,set,其它类型
    @param {boolean} isJson: 返回结果是否需要反 json 化
    @return {unicode}: 返回转成 unicode 的字符串,且呈现便于人阅读的模式
        本函数与 to_unicode(value, to_human=True) 函数的区别是: to_unicode 只转换字符串,且 (list,tuple,set, dict) 不改变类型。
        而本函数会将所有类型转成字符串,包括 (list,tuple,set, dict) 类型,且这些类型会尽量美化输出。
    '''
    # 先将可以转成字符串的都先转成字符串
    value = to_unicode(value, to_human=True)
    if isinstance(value, basestring):
        value = value.strip()
        # json 格式的,尽量按 json 格式美化一下输出
        if isJson or (value.startswith('{') and value.endswith('}')) or (value.startswith('[') and value.endswith(']')):
            value = to_json(value)
    # list,tuple,set,dict 类型,按 json 格式美化一下输出
    if isinstance(value, (list,tuple,set, dict)):
        return json.dumps(value, indent=2, ensure_ascii=False)
    # 其它类型,可以部分地交给 to_unicode 处理
    return value

def to_json(value, **kwargs):
    u'''
    @summary: 将字符串转成json
    @param {string} value: 要转成json的字符串
    @param {bool} raise_error: 遇到解析异常时,是否抛出异常信息。为 True则会抛出异常信息,否则不抛出(默认抛出)
    @return {dict}: 返回转换后的类型
    '''
    if isinstance(value, basestring):
        try:
            value = json.loads(value)
        except:
            logging.warn(u'将字符串json反序列化出错,下面将转eval处理:%s' % value)
            try:
                value = eval(value)
            except:
                logging.error(u'将字符串json反序列化出错,无法处理:%s' % value)
                raise_error = kwargs.get('raise_error', True)
                if raise_error:
                    raise RuntimeError(u"to_json转换字符串失败:%s" % (value))
    return value

def json2str(value, **kwargs):
    u'''
    @summary: 将 dict 类型的内容转成json格式的字符串
    @param {dict} value: 要转成json字符串的内容
    @param {bool} raise_error: 遇到解析异常时,是否抛出异常信息。为 True则会抛出异常信息,否则不抛出(默认抛出)
    @return {string}: 返回转换后的字符串
    '''
    try:
        value = to_unicode(value, all2str='time') # 兼容 GBK 编码的中文字符
        value = json.dumps(value)
    except:
        logging.warn(u'将对象进行json序列化出错:%s' % value)
        raise_error = kwargs.get('raise_error', True)
        if raise_error:
            raise RuntimeError(mod(u"to_json转换字符串失败:%s", value))
        else:
            try:
                value = str(value)
            except:
                logging.error(u'将对象格式化成字符串出错:%s' % value)
    return value

def mod(sour, *args, **kwargs):
    u'''
    @summary: 相当于使用“%”格式化字符串
    @param {string} sour: 要格式化的字符串
    @param {任意} param: 要放入字符串的参数,多个则用 tuple 括起来
    @param {boolean} to_human: 是否要将字符串转码成便于人阅读的编码(将 “\u65f6”,“\xE5\x8C\x85”等字符转为人可以阅读的文字)
    @return {string}: 返回格式化后的字符串(unicode编码),即返回: sour %  param
    '''
    # 参数 param 允许传 None,0,False 等值
    has_param = False
    if len(args) > 0:
        param = args[0]
        has_param = True
    elif 'param' in kwargs:
        param = kwargs.pop('param')
        has_param = True
    # 按 sour 的类型执行 mod
    if isinstance(sour, str):
        sour = to_str(sour, **kwargs)
        if has_param:
            return sour % to_str(param, **kwargs)
        else:
            return sour
    elif isinstance(sour, unicode):
        if has_param:
            return sour % to_unicode(param, **kwargs)
        else:
            return sour
    else:
        return unicode(sour)


def gzip_encode(content):
    u'''
    @summary: 使用 gzip 压缩字符串
    @param {string} content: 明文字符串
    @return {string}: 压缩后的字符串
    '''
    if not isinstance(content, basestring):
        content = json2str(content)
    zbuf = StringIO.StringIO()
    zfile = gzip.GzipFile(mode='wb', compresslevel=9, fileobj=zbuf)
    zfile.write(content)
    zfile.close()
    return zbuf.getvalue()

def gzip_decode(content):
    u'''
    @summary: 使用 gzip 解压字符串
    @param {string} content: 压缩后的字符串
    @return {string}: 解压出来的明文字符串
    '''
    zfile = gzip.GzipFile(fileobj=StringIO.StringIO(content))
    result = zfile.read()
    zfile.close()
    return result


def zlib_encode(content):
    u'''
    @summary: 使用 zlib 压缩字符串
    @param {string} content: 明文字符串
    @return {string}: 压缩后的字符串
    '''
    if not isinstance(content, basestring):
        content = json2str(content)
    return zlib.compress(content, zlib.Z_BEST_COMPRESSION)

def zlib_decode(content):
    u'''
    @summary: 使用 zlib 解压字符串
    @param {string} content: 压缩后的字符串
    @return {string}: 解压出来的明文字符串
    '''
    return zlib.decompress(content)
